查看原文
其他

给妹子讲python-S01E18初探函数作用域

给妹子讲python Python爱好者社区 2019-04-07

作者:酱油哥/ 清华程序猿      
微信公众号: python数据科学家
知乎专栏: 《给妹子讲python》
https://zhuanlan.zhihu.com/c_147297848


前文传送门:

给妹子讲python-S01E01好用的列表

给妹子讲python-S01E02学会用字典

给妹子讲python-S01E03元组的使用

给妹子讲python-S01E04容器遍历和列表解析式

给妹子讲python-S01E05字符串的基本用法

给妹子讲python-S01E06字符串用法进阶

给妹子讲python-S01E07字符编码历史观:从ASCII到Unicode

给妹子讲python-S01E08理清python中的字符编码方法

给妹子讲python-S01E09文件操作小意思

给妹子讲python-S01E10动态类型与共享引用

给妹子讲python-S01E11赋值与对象拷贝

给妹子讲python-S01E12循环迭代初体验

给妹子讲python-S01E13循环迭代高级技巧

给妹子讲python-S01E14可迭代对象和迭代器

给妹子讲python-S01E15迭代环境

给妹子讲python-S01E16生成器的使用

给妹子讲python-S01E17函数的基本特征

【要点抢先看】

1.变量的四种作用域
2.函数变量的LEGB作用域搜索机制
3.利用global关键字进行全局变量修改

上一小节我们引入了函数的概念,这一节我们开始接触函数里的一个非常重要的话题:变量的作用域。

当你在一个程序中使用变量名时,python创建、改变或查找变量名都是在所谓的命名空间中进行的,也就是我们要说的变量的作用域。在代码中给一个变量赋值的地方决定了这个变量将存在于哪一个命名空间,也就是他的可见范围。

def之中的变量名和def之外的变量名并不冲突,一个在def之外被赋值(例如,在另外一个def之中或者在模块文件的顶层)的变量X与在这个def之中赋值的变量X是完全不同的变量。

所以我们看出,变量的作用域完全是由变量在程序文件中源代码的位置而决定,而不是由函数调用决定。

【妹子说】好啦,说了这么多概念,还是用例子说话吧!

x = 99
def func():
    x = 88
    print(x)

func()
print(x)

88
99

这里就可以看出,在这个模块文件中:语句X=99,我们创建了一个名为X的全局变量(在这个函数所在的模块文件中可见),但是X=88这个赋值语句创建了一个本地变量X(只在def语句内是可见的)。

尽管这两个变量都是X,但是他们作用域可以把它们区别开来。实际上,函数的作用域有助于防止程序之中变量名的冲突,并且有助于函数成为更加独立的程序单元。

【妹子说】哦,我大概对作用域有那么点概念了。

那下面我们接着展开具体介绍函数的四个作用域:LEGB,即L本地作用域,E内嵌作用域,G全局作用域和B内置作用域。

在一个函数中定义的是本地作用域,而模块(也就是一个xxx.py文件)中定义的是全局作用域。而内置作用域,我们使用时是直接使用变量名而不需要导入任何模块,比如一些内置的函数名:print等等

这里再强调一下python中所谓的全局作用域:

全局作用域的作用范围仅限于单个文件,别被全局二字所迷惑,这里的全局指的是一个文件的顶层的变量名仅对于这个文件内部的代码而言是全局的,在python中听到全局,你就应该想到模块二字。

变量名由模块文件隔开,并且必须精确的导入一个模块文件才能够使用这个文件中使用的变量名。

再说说本地作用域:每次对函数的调用都创建一个新的本地作用域,赋值的变量名除非声明为全局变量或非本地变量,否则均为本地变量。在默认的情况下,所有函数定义的内部变量名都位于本地作用域(与函数调用相关的)内。

再来看一个例子来演示一下这两种作用域:

x = 99
def func(y):
    z = x + y
    return z

print(func(1))

100

这个例子中出现了全局作用域内变量名:x和func

因为x是在模块文件顶层注册的,所以他是全局变量;他能够在函数内部进行引用,仅仅是引用变量不需要进行全局变量声明。

也有本地作用域变量名,y,z

z和参数y都是本地变量,只在函数运行时存在,因为他们都是在函数定义内部进行赋值的,前面我们说过函数的参数也是通过赋值进行传递的。

这种变量名隔离机制存在的意义在于本地变量是作为临时的变量名,只有在函数运行的时候才需要它们,例如,y和z都只存在于函数内部,这些变量名不会与模块命名空间内的变量名(同理,与其他函数内的变量名)产生冲突。

【妹子说】那么如果一段程序中,不同作用域内的几个变量名称相同怎么办?

好问题!这正是python的LEGB变量名搜索机制要解决的问题。

当在python中使用某个变量名时,python按照L-E-G-B的顺序依次搜索四个作用域,L本地作用域,E即上一层def或者lambda的本地作用域,之后是全局作用域G,最后是内置作用域B,并且在第一处能找到作用名的地方停下来,如果变量名在这一次搜索中没有找到,python会报错。

因此按照LEGB法则中规定的变量搜索顺序,在本地作用域中的变量名是会在本地作用域中覆盖在全局作用域和内置作用域中有相同变量名的变量,全局变量名会覆盖内置的同名变量名。

【妹子说】话不多说,上例子

x = 88
def func():
    x = 99
    print(x)

func()
print(x)

99
88

在这一段程序中,本地变量名x覆盖了全局变量名x,此时本地和全局的两个变量虽然都叫x,但他们是完全不同的变量。

def func():
    open = 1
    open('test.txt')

func()

Traceback (most recent call last):
 File "E:/12homework/12homework.py", line 5, in <module>
   func()
 File "E:/12homework/12homework.py", line 3, in func
   open('test.txt')
TypeError: 'int' object is not callable

这个例子中,本地作用域中的变量名open就覆盖了内置作用域中的变量名open,因此再使用open去打开文件,此时的操作就无法使用,因为文件打开的open函数变量被open=1这个本地数值变量覆盖了。

强调一点:这里我们提到的只是在本地作用域去引用或者覆盖全局变量和内置变量。

但是,请注意!如果试图去修改,即在函数内部试图改变函数外部声明的值,那就得用global和nonlocal关键字了。

global关键字

之前我们说过python中的变量不用声明,直接赋值使用,但是这个global关键字看上去就像一个声明,但是他不是一个类型的声明,而是一个变量命名空间的声明,它告诉python函数打算生成一个或多个全局变量。应用他,就可以在函数内部对全局变量进行引用和修改

x = 88
def func():
    global x
    x = 99

func()
print(x)

99

在这个例子中,我们对X加了一个global声明,以便在def之内引用并修改位于全局的变量x,而不是产生一个新的本地变量x并将其覆盖

我们再看一个综合的例子,串联起刚刚我们提到的几个知识点

x,y,z = 1,2,3

def all_global():
    global x
    x = y + z

all_global()
print(x)

5

这个例子中,x,y,z都是全局变量,y和z只是引用值,而对于x,我们想改变他的值,因此用了global进行引用声明。

【妹子说】恩,作用域的内容也是不少呀,LEGB,本地作用域、全局作用域、内置作用域,包括他们的引用索引顺序,但是好像掉了一条吧。

对,还有嵌套作用域,这个是一个更加pythonic的内容,我们下一节单独花一节的篇幅进行介绍。

Python爱好者社区历史文章大合集

Python爱好者社区历史文章列表(每周append更新一次)

福利:文末扫码立刻关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

小编的Python入门免费视频课程!!!

【最新免费微课】小编的Python快速上手matplotlib可视化库!!!

崔老师爬虫实战案例免费学习视频。

陈老师数据分析报告制作免费学习视频。

玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存